#ifndef INC_REQ_newGA
#define INC_REQ_newGA
#include "newGA.hh"
#include <math.h>
#include "tinyxml2.h"

#define NULO 0
#define NEUTRO 1
#define INVERSO -1

using namespace tinyxml2;

skeleton newGA
{

    // Problem ---------------------------------------------------------------

    Problem::Problem() {
    }

    ostream& operator<<(ostream& os, const Problem & pbm) {
        int filas = pbm._pairingsVuelos.dimX(),
                columnas = pbm._pairingsVuelos.dimY();

        os << "Filas: " << filas << endl;
        os << "Columnas: " << columnas << endl;

        os << "Pairings: " << endl;

        for (int i = 0; i < filas; i++) {
            for (int j = 0; j < columnas; j++) {
                os << pbm._pairingsVuelos(i, j) << " ";
            }
            os << endl;
        }

        os << "Costos: ";

        for (int i = 0; i < pbm._vectorCostos.size(); i++) {
            os << pbm._vectorCostos[i] << " ";
        }
        os << endl;

        return os;
    }

    istream& operator>>(istream& is, Problem & pbm) {
        pbm.importarTXT(is, pbm);
        return is;
    }

    void Problem::importarTXT(istream& is, Problem & pbm) {
        cout << "Importando datos..." << endl;

        int cantFilas, cantColumnas, cantUnos = 0;
        int cantColumnasCubiertas, columnaCubierta;

        // number of rows (m)
        is >> cantFilas;
        // number of columns (n)
        is >> cantColumnas;

        cout << "Filas: " << cantFilas << endl;
        cout << "Columnas: " << cantColumnas << endl;

        cout << "Importando costos..." << endl;

        pbm._vectorCostos = Rarray<int>(cantColumnas);

        // the cost of each column c(j),j=1,...,n
        for (int i = 0; i < cantColumnas; i++) {
            is >> pbm._vectorCostos[i];
        }

        cout << "Importando pairings..." << endl;

        pbm._pairingsVuelos = Matrix<int>(cantFilas, cantColumnas, NULO, NEUTRO, INVERSO);
        for (int i = 0; i < cantFilas; i++)
            for (int j = 0; j < cantColumnas; j++)
                pbm._pairingsVuelos(i, j) = 0;

        // for each row i (i=1,...,m)
        for (int i = 0; i < cantFilas; i++) {
            // the number of columns which cover row i
            is >> cantColumnasCubiertas;

            // followed by a list of the columns which cover row i
            for (int j = 0; j < cantColumnasCubiertas; j++) {
                is >> columnaCubierta;
                pbm._pairingsVuelos(i, columnaCubierta - 1) = 1;
            }

            cantUnos += cantColumnasCubiertas;
        }

        cout << "Densidad: " << (cantUnos * 100) / (cantFilas * cantColumnas) << "%" << endl;

        cout << "Fin." << endl;
    }

    // importa archivo de Bahadir
    void Problem::importarXML(istream& is, Problem & pbm) {
        XMLDocument doc;
        doc.LoadFile("instancias/310_PairsHQ.xml");

        XMLElement *ds = doc.FirstChildElement("ds");
        XMLElement *rl = ds->FirstChildElement("rl");
        XMLElement *cl = ds->FirstChildElement("cl");

        int cantFilas = rl->IntAttribute("numOfRows");
        int cantColumnas = cl->IntAttribute("numOfCols");

        cout << "Filas: " << cantFilas << endl;
        cout << "Columnas: " << cantColumnas << endl;

        pbm._vectorCostos = Rarray<int>(cantColumnas);
        pbm._pairingsVuelos = Matrix<int>(cantFilas, cantColumnas, NULO, NEUTRO, INVERSO);
        for (int i = 0; i < cantFilas; i++)
            for (int j = 0; j < cantColumnas; j++)
                pbm._pairingsVuelos(i, j) = 0;

        int columna = 0;
        char value[80];
        for (XMLElement *nodo = cl->FirstChildElement("c"); nodo != NULL; nodo = nodo->NextSiblingElement("c"), columna++) {
            pbm._vectorCostos[columna] = nodo->IntAttribute("c");
            strcpy(value, nodo->GetText());
            for (char *fila = strtok(value, ","); fila != NULL; fila = strtok(NULL, ","))
                pbm._pairingsVuelos(atoi(fila), columna) = 1;
        }

        bool error = false;
        for (int i = 0; i < cantFilas; i++) {
            bool filaCubierta = false;
            for (int j = 0; j < cantColumnas && !filaCubierta; j++) {
                filaCubierta = pbm._pairingsVuelos(i, j) == 1;
            }
            if (!filaCubierta) {
                error = true;
                cout << "Vuelo no cubierto: " << i << endl;
            }
        }
        if (error) {
            exit(1);
        }
    }

    Problem & Problem::operator=(const Problem & pbm) {
        _vectorCostos.remove();
        _pairingsVuelos.remove();

        _vectorCostos = pbm._vectorCostos;
        _pairingsVuelos = pbm._pairingsVuelos;

        return *this;
    }

    bool Problem::operator==(const Problem & pbm) const {
        if (_vectorCostos.size() != pbm._vectorCostos.size()) {
            return false;
        }
        for (int i = 0; i < _vectorCostos.size(); i++) {
            if (_vectorCostos[i] != pbm._vectorCostos[i]) {
                return false;
            }
        }

        if (_pairingsVuelos.dimX() != pbm._pairingsVuelos.dimX() ||
                _pairingsVuelos.dimY() != pbm._pairingsVuelos.dimY()) {
            return false;
        }
        for (int i = 0; i < _pairingsVuelos.dimX(); i++) {
            for (int j = 0; j < _pairingsVuelos.dimY(); j++) {
                if (_pairingsVuelos(i, j) != pbm._pairingsVuelos(i, j)) {
                    return false;
                }
            }
        }

        return true;
    }

    bool Problem::operator!=(const Problem & pbm) const {
        return !(*this == pbm);
    }

    Direction Problem::direction() const {
        return minimize;
    }

    Problem::~Problem() {
    }

    double Problem::getCosto(const int& p) const {
        return _vectorCostos[p];
    }

    Matrix<int> Problem::getPairings() const {
        return _pairingsVuelos;
    }

    int Problem::tam() const {
        return _vectorCostos.size();
    }
    // Solution --------------------------------------------------------------

    Solution::Solution(const Problem & pbm) : _pbm(pbm) {
        int tam = pbm.tam();
        _pairings = Rarray<int>(tam);
        for (int i = 0; i < tam; i++) {
            _pairings[i] = 0;
        }
    }

    const Problem & Solution::pbm() const {
        return _pbm;
    }

    Solution::Solution(const Solution & sol) : _pbm(sol.pbm()) {
        *this = sol;
    }

    istream& operator>>(istream& is, Solution & sol) {
        for (int i = 0; i < sol.pbm().tam(); i++)
            is >> sol._pairings[i];
        return is;
    }

    ostream& operator<<(ostream& os, const Solution & sol) {
        for (int i = 0; i < sol._pairings.size(); i++) {
            if (sol._pairings[i] == 1)
                os << " " << i;
        }

        // Cuento repetidos
        Matrix<int> vp = sol.pbm().getPairings();
        int cantFilas = vp.dimX();
        int repetidos = 0;
        for (int fila = 0; fila < cantFilas; fila++) {
            int sumaFila = 0;
            for (int columna = 0; columna < sol._pairings.size(); columna++) {
                sumaFila += sol._pairings[columna] * vp(fila, columna);
            }
            if (sumaFila > 1) { //hay repetidos
                repetidos += sumaFila - 1;
            }
        }

        os << endl << "Repetidos: " << repetidos;

        return os;
    }

    NetStream& operator<<(NetStream& ns, const Solution & sol) {
        for (int i = 0; i < sol.pbm().tam(); i++)
            ns << sol._pairings[i];
        return ns;
    }

    NetStream& operator>>(NetStream& ns, Solution & sol) {
        for (int i = 0; i < sol.pbm().tam(); i++)
            ns >> sol._pairings[i];
        return ns;
    }

    Solution & Solution::operator=(const Solution & sol) {
        _pairings = sol._pairings;
        return *this;
    }

    bool Solution::operator==(const Solution & sol) const {
        if (sol.pbm() != _pbm) {
            return false;
        }
        for (int i = 0; i < _pairings.size(); i++) {
            if (_pairings[i] != sol._pairings[i]) {
                return false;
            }
        }
        return true;
    }

    bool Solution::operator!=(const Solution & sol) const {
        return !(*this == sol);
    }

    void Solution::initialize() {
        Matrix<int> vp = _pbm.getPairings();
        int cantFilas = _pbm.getPairings().dimX();
        Rarray<bool> cubierto = Rarray<bool>(cantFilas);
        for (int i = 0; i < cantFilas; i++) cubierto[i] = false;

        for (int i = 0; i < cantFilas; i++) {
            if (!cubierto[i]) {
                Rlist<int> cubrenVuelo = Rlist<int>();

                for (int j = 0; j < vp.dimY(); j++) {
                    if (vp(i, j)) {
                        int* k = new int[1];
                        k[0] = j;
                        cubrenVuelo.append(k);
                    }
                }
                int elegida = rand_int(0, cubrenVuelo.size() - 1);
                for (int j = 0; j < cantFilas; j++) {
                    if (vp(j, cubrenVuelo[elegida])) cubierto[j] = true;
                }
                _pairings[cubrenVuelo[elegida]] = 1;
            }
        }
    }

    double Solution::fitness() {
        double fitness = 0.0;
        for (int i = 0; i < _pairings.size(); i++) {
            fitness += _pairings[i] * _pbm.getCosto(i);
        }

        //TODO cargar de config penalizacion?
//                double PENALIZACION = 10.0;
//
//                // Cuento repetidos
//                Matrix<int> vp = _pbm.getPairings();
//                int cantFilas = vp.dimX();
//                int repetidos = 0;
//                for (int fila = 0; fila < cantFilas; fila++) {
//                    int sumaFila = 0;
//                    for (int columna = 0; columna < _pairings.size(); columna++) {
//                        sumaFila += _pairings[columna] * vp(fila, columna);
//                    }
//                    if (sumaFila > 1) { //hay repetidos
//                        repetidos += sumaFila - 1;
//                    }
//                }
//
//
//                fitness += PENALIZACION * repetidos;

        return fitness;
    }

    char *Solution::to_String() const {
        return (char *) _pairings.get_first();
    }

    void Solution::to_Solution(char *_string_) {
        int *ptr = (int *) _string_;
        for (int i = 0; i < _pairings.size(); i++) {
            _pairings[i] = *ptr;
            ptr++;
        }
    }

    unsigned int Solution::size() const {
        return (_pairings.size() * sizeof (int));
    }

    Solution::~Solution() {
    }

    /**
     * Retorna true si la solucion completa es viable, esto es,
     * si todos los vuelos estan cubiertos por al menos un pairing.
     * @return true si la solucion es viable, false en otro caso
     */
    bool Solution::esViable() {
        Matrix<int> vp = _pbm.getPairings();
        int cantFilas = vp.dimX();
        for (int fila = 0; fila < cantFilas; fila++) {
            bool viable = false;
            for (int columna = 0; columna < _pairings.size() && !viable; columna++) {
                viable = (_pairings[columna] * vp(fila, columna) == 1);
            }
            if (!viable) return false;
        }
        return true;
    }

    int & Solution::gen(const int& index) {
        return _pairings[index];
    }

    /**
     * Corrige la solucion completa para que sea viable.
     */
    void Solution::corregir() {
        Matrix<int> vp = _pbm.getPairings();
        int cantFilas = vp.dimX();
        for (int fila = 0; fila < cantFilas; fila++) {
            bool filaCubierta = false;
            for (int columna = 0; columna < _pairings.size() && !filaCubierta; columna++) {
                filaCubierta = (_pairings[columna] * vp(fila, columna) == 1);
            }
            if (!filaCubierta) {
                bool encontre = false;
                for (int col = 0; col < _pairings.size() && !encontre; col++) {
                    encontre = (vp(fila, col) == 1);
                    if (encontre) _pairings[col] = 1;
                }
            }
        }
    }

    // UserStatistics -------------------------------------------------------

    UserStatistics::UserStatistics() {
    }

    ostream& operator<<(ostream& os, const UserStatistics & userstat) {
        os << "\n---------------------------------------------------------------" << endl;
        os << "                   STATISTICS OF TRIALS                   	 " << endl;
        os << "------------------------------------------------------------------" << endl;
        os << "tr"
                << "\t" << "best_cost"
                << "\t\t" << "worst_cost"
                << "\t\t" << "iters_trial"
                << "\t\t" << "iter_best"
                << "\t\t" << "time_best"
                << "\t\t" << "time_spent" << endl;

        for (int i = 0; i < userstat.result_trials.size(); i++) {
            os << endl
                    << userstat.result_trials[i].trial
                    << "\t" << userstat.result_trials[i].best_cost_trial
                    << "\t\t" << userstat.result_trials[i].worst_cost_trial
                    << "\t\t\t" << userstat.result_trials[i].nb_evaluation_best_found_trial
                    << "\t\t\t" << userstat.result_trials[i].nb_iteration_best_found_trial
                    << "\t\t\t" << userstat.result_trials[i].time_best_found_trial
                    << "\t\t" << userstat.result_trials[i].time_spent_trial;
        }
        os << endl << "------------------------------------------------------------------" << endl;
        return os;
    }

    UserStatistics & UserStatistics::operator=(const UserStatistics & userstats) {
        result_trials = userstats.result_trials;
        return (*this);
    }

    void UserStatistics::update(const Solver & solver) {
        if ((solver.pid() != 0) || (solver.end_trial() != true)
                || ((solver.current_iteration() != solver.setup().nb_evolution_steps())
                && !terminateQ(solver.pbm(), solver, solver.setup())))
            return;

        struct user_stat *new_stat;

        if ((new_stat = (struct user_stat *) malloc(sizeof (struct user_stat))) == NULL)
            show_message(7);
        new_stat->trial = solver.current_trial();
        new_stat->nb_iteration_best_found_trial = solver.iteration_best_found_in_trial();
        new_stat->nb_evaluation_best_found_trial = solver.evaluations_best_found_in_trial();
        new_stat->worst_cost_trial = solver.worst_cost_trial();
        new_stat->best_cost_trial = solver.best_cost_trial();
        new_stat->time_best_found_trial = solver.time_best_found_trial();
        new_stat->time_spent_trial = solver.time_spent_trial();

        result_trials.append(*new_stat);
    }

    void UserStatistics::clear() {
        result_trials.remove();
    }

    UserStatistics::~UserStatistics() {
        result_trials.remove();
    }

    // Intra_operator  --------------------------------------------------------------

    Intra_Operator::Intra_Operator(const unsigned int _number_op) : _number_operator(_number_op), probability(NULL) {
    }

    unsigned int Intra_Operator::number_operator() const {
        return _number_operator;
    }

    Intra_Operator * Intra_Operator::create(const unsigned int _number_op) {
        switch (_number_op) {
            case 0: return new Crossover();
                break;
            case 1: return new Mutation();
                break;
            case 2: return new Perturbation();
                break;
        }
    }

    ostream& operator<<(ostream& os, const Intra_Operator & intra) {
        switch (intra.number_operator()) {
            case 0: os << (Crossover&) intra;
                break;
            case 1: os << (Mutation&) intra;
                break;
            case 2: os << (Perturbation&) intra;
                break;
        }
        return os;
    }

    Intra_Operator::~Intra_Operator() {
    }

    //  Crossover_PMX1:Intra_operator -------------------------------------------------------------

    Crossover::Crossover() : Intra_Operator(0) {
        probability = new float[1];
    }

    void Crossover::cross(Solution& sol1, Solution & sol2) const // dadas dos soluciones de la poblacion, las cruza
    {
        //cruzamiento uniforme
        for (int j = 0; j < sol1.pbm().tam(); j++) {
            if (sol1.gen(j) != sol2.gen(j) && rand01() <= probability[1]) {
                int tmp = sol1.gen(j);
                sol1.gen(j) = sol2.gen(j);
                sol2.gen(j) = tmp;
            }
        }
        sol1.corregir();
        sol2.corregir();
    }

    void Crossover::execute(Rarray<Solution*>& sols) const {
        for (int i = 0; i + 1 < sols.size(); i += 2)
            if (rand01() <= probability[0]) cross(*sols[i], *sols[i + 1]);
    }

    ostream& operator<<(ostream& os, const Crossover & cross) {
        os << "Crossover." << " Probability: "
                << cross.probability[0]
                << endl;
        return os;
    }

    void Crossover::RefreshState(const StateCenter & _sc) const {
        _sc.set_contents_state_variable("_crossover_probability", (char *) probability, 1, sizeof (float));
    }

    void Crossover::UpdateFromState(const StateCenter & _sc) {
        unsigned long nbytes, length;
        _sc.get_contents_state_variable("_crossover_probability", (char *) probability, nbytes, length);
    }

    void Crossover::setup(char line[MAX_BUFFER]) {
        int op;
        sscanf(line, " %d %f %f", &op, &probability[0], &probability[1]);
        assert(probability[0] > 0);
        cout << "Probabilidad de cruzamiento: " << probability[0] << endl;
        cout << "Probabilidad cruzamiento uniforme: " << probability[1] << endl;
    }

    Crossover::~Crossover() {
        delete [] probability;
    }

    //  Mutation: Sub_operator -------------------------------------------------------------

    Mutation::Mutation() : Intra_Operator(1), _prob_unos(0), _prob_ceros(0) {
        probability = new float[3];
    }

    void Mutation::mutate(Solution & sol) const {
        float prob_ceros = probability[2];
        float prob_unos = probability[1];
        int mutantes = rand_int(0, sol.pbm().tam() - 1);
        //utilizo las probabilidades para mutar,
        for (int j = 0; j < sol.pbm().tam(); j++) {
            if (mutantes > 0 && sol.gen(j) == 0 && rand01() <= prob_ceros) {
                sol.gen(j) = 1;
                mutantes--;
            } else if (mutantes > 0 && sol.gen(j) == 1 && rand01() <= prob_unos) {
                sol.gen(j) = 0;
                mutantes--;
            }
            //TODO esto no asegura que se muten exactamente los genes obtenidos en primera instancia
        }
        //agrego pairings hasta que sea viable
        sol.corregir();
    }

    void Mutation::execute(Rarray<Solution*>& sols) const {
        //busco el inidividuo mas adaptado y me quedo luego con la cantidad de ceros y unos
        //para calcular las probabilidades
        Solution* best = NULL;
        double mejorFitness = 0.0;
        for (int i = 0; i < sols.size(); i++) {
            double fitnessActual = sols[i]->fitness();
            if (best == NULL || fitnessActual <= mejorFitness) {
                best = sols[i];
                mejorFitness = fitnessActual;
            }
        }
        //cuento los unos
        int unos = 0;
        for (int i = 0; i < best->pbm().tam(); i++)
            unos += best->gen(i);

        //seteo las probs
        probability[1] = (float)unos / best->pbm().tam();
        probability[2] = 1 - probability[1];
        //realizamos la mutacion
        for (int i = 0; i < sols.size(); i++) {
            if (rand01() <= probability[0])
                mutate(*sols[i]);
        }

    }

    ostream& operator<<(ostream& os, const Mutation & mutation) {
        os << "Mutation." << " Probability: "
                << mutation.probability[0]
                << endl;
        return os;
    }

    void Mutation::setup(char line[MAX_BUFFER]) {
        int op;
        sscanf(line, " %d %f", &op, &probability[0]);
        assert(probability[0] >= 0);
        cout << "Probabilidad de mutacion: " << probability[0] << endl;
    }

    void Mutation::RefreshState(const StateCenter & _sc) const {
        _sc.set_contents_state_variable("_mutation_probability", (char *) probability, 1, sizeof (probability));
    }

    void Mutation::UpdateFromState(const StateCenter & _sc) {
        unsigned long nbytes, length;
        _sc.get_contents_state_variable("_mutation_probability", (char *) probability, nbytes, length);
    }

    Mutation::~Mutation() {
        delete [] probability;
    }

    // Perturbation:Intra_operator ---------------------------------------------------------

    Perturbation::Perturbation() : Intra_Operator(2) {
        probability = new float[2];
    }

    Perturbation::~Perturbation() {
        delete [] probability;
    }

    ostream& operator<<(ostream& os, const Perturbation& perturbation) {
        os << "Perturbation" << endl;
        return os;
    }

    void Perturbation::perturbate(Solution& sol) const {
        for (int i = 0; i < sol.pbm().tam(); i++) {
            if (sol.gen(i) == 1 && rand01() <= probability[1]) {
                sol.gen(i) = 0;
            }
        }
        sol.corregir();
    }

    void Perturbation::execute(Rarray<Solution*>& sols) const {
        for (int i = 0; i < sols.size(); i++) {
            if (rand01() <= probability[0]) {
                perturbate(*sols[i]);
            }
        }
    }

    void Perturbation::setup(char line[MAX_BUFFER]) {
        int op;
        sscanf(line, " %d %f %f", &op, &probability[0], &probability[1]);
        assert(probability[0] >= 0);
        assert(probability[1] >= 0);
        cout << "Probabilidad de perturbacion: " << probability[0] << endl;
    }

    void Perturbation::RefreshState(const StateCenter& _sc) const {
//        _sc.set_contents_state_variable("_perturbation_probability", (char *) probability, 1, sizeof (probability));
    }

    void Perturbation::UpdateFromState(const StateCenter & _sc) {
//        unsigned long nbytes, length;
//        _sc.get_contents_state_variable("_perturbation_probability", (char *) probability, nbytes, length);
    }

    // StopCondition_1 -------------------------------------------------------------------------------------

    StopCondition_1::StopCondition_1() : StopCondition() {
    }

    bool StopCondition_1::EvaluateCondition(const Problem& pbm, const Solver& solver, const SetUpParams & setup) {
        //		return solver.current_time_spent() > 10000;;
        return false;
    }

    StopCondition_1::~StopCondition_1() {
    }

    //------------------------------------------------------------------------
    // Specific methods ------------------------------------------------------
    //------------------------------------------------------------------------

    bool terminateQ(const Problem& pbm, const Solver& solver,
            const SetUpParams & setup) {
        StopCondition_1 stop;
        return stop.EvaluateCondition(pbm, solver, setup);
    }
}
#endif
